<?php
/**
 * reference-def-processor.php
 *
 * This file implements the processor reference definition part of the CREATE TABLE statements.
 *
 * Copyright (c) 2010-2012, Justin Swanhart
 * with contributions by André Rothe <arothe@phosco.info, phosco@gmx.de>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
if (!defined('HAVE_REF_DEF_PROCESSOR')) {
	require_once(dirname(__FILE__) . '/abstract-processor.php');
	require_once(dirname(__FILE__) . '/index-column-list-processor.php');
	require_once(dirname(__FILE__) . '/../expression-types.php');

	/**
	 *
	 * This class processes the reference definition part of the CREATE TABLE statements.
	 *
	 * @author arothe
	*/
	class ReferenceDefinitionProcessor extends AbstractProcessor {

		protected function buildReferenceDef($expr, $base_expr, $key) {
			$expr['till'] = $key;
			$expr['base_expr'] = $base_expr;
			return $expr;
		}

		public function process($tokens) {

			$expr = array('type' => ExpressionType::REFERENCE, 'base_expr' => false, 'sub_tree' => array());
			$base_expr = '';
			
			foreach ($tokens as $key => $token) {

				$trim = trim($token);
				$base_expr .= $token;

				if ($trim === '') {
					continue;
				}

				$upper = strtoupper($trim);

				switch ($upper) {

					case ',':
						# we stop on a single comma
						# or at the end of the array $tokens
						$expr = $this->buildReferenceDef($expr, trim(substr($base_expr, 0, -strlen($token))), $key-1);
						break 2;
							
					case 'REFERENCES':
						$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
						$currCategory = $upper;
						break;

					case 'MATCH':
						if ($currCategory === 'REF_COL_LIST') {
							$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
							$currCategory = 'REF_MATCH';
							continue 2;
						}
						# else?
						break;

					case 'FULL':
					case 'PARTIAL':
					case 'SIMPLE':
						if ($currCategory === 'REF_MATCH') {
							$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
							$expr['match'] = $upper;
							$currCategory = 'REF_COL_LIST';
							continue 2;
						}
						# else?
						break;

					case 'ON':
						if ($currCategory === 'REF_COL_LIST') {
							$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
							$currCategory = 'REF_ACTION';
							continue 2;
						}
						# else ?
						break;

					case 'UPDATE':
					case 'DELETE':
						if ($currCategory === 'REF_ACTION') {
							$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
							$currCategory = 'REF_OPTION_'.$upper;
							continue 2;
						}
						# else ?
						break;

					case 'RESTRICT':
					case 'CASCADE':
						if (strpos($currCategory, 'REF_OPTION_') === 0) {
							$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
							$expr['on_' . strtolower(substr($currCategory, -6))] = $upper;
							continue 2;
						}
						# else ?
						break;

					case 'SET':
					case 'NO':
						if (strpos($currCategory, 'REF_OPTION_') === 0) {
							$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
							$expr['on_' . strtolower(substr($currCategory, -6))] = $upper;
							$currCategory = 'SEC_' . $currCategory;
							continue 2;
						}
						# else ?
						break;

					case 'NULL':
					case 'ACTION':
						if (strpos($currCategory, 'SEC_REF_OPTION_') === 0) {
							$expr['sub_tree'][] = array('type' => ExpressionType::RESERVED, 'base_expr' => $trim);
							$expr['on_' . strtolower(substr($currCategory, -6))] .= ' ' . $upper;
							$currCategory = 'REF_COL_LIST';
							continue 2;
						}
						# else ?
						break;

					default:

						switch ($currCategory) {

							case 'REFERENCES':
								if ($upper[0] === '(' && substr($upper, -1) === ')') {
									# index_col_name list
									$processor = new IndexColumnListProcessor();
									$cols = $processor->process($this->removeParenthesisFromStart($trim));
									$expr['sub_tree'][] = array('type' => ExpressionType::COLUMN_LIST, 'base_expr' => $trim,
											'sub_tree' => $cols);
									$currCategory = 'REF_COL_LIST';
									continue 3;
								}
								# foreign key reference table name
								$expr['sub_tree'][] = array('type' => ExpressionType::TABLE, 'table'=>$trim, 'base_expr'=>$trim, 'no_quotes'=>$this->revokeQuotation($trim));
								continue 3;

							default:
								# else ?
								break;
						}
						break;
				}
			}

			if (!isset($expr['till'])) {
				$expr = $this->buildReferenceDef($expr, trim($base_expr), -1);
			}
			return $expr;
		}
	}
	define('HAVE_REF_DEF_PROCESSOR', 1);
}
